// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/profiler/win32_stack_frame_unwinder.h"

#include <windows.h>

#include <utility>

#include "base/macros.h"
#include "base/memory/ptr_util.h"

namespace base {

// Win32UnwindFunctions -------------------------------------------------------

const HMODULE ModuleHandleTraits::kNonNullModuleForTesting = reinterpret_cast<HMODULE>(static_cast<uintptr_t>(-1));

// static
bool ModuleHandleTraits::CloseHandle(HMODULE handle)
{
    if (handle == kNonNullModuleForTesting)
        return true;

    return ::FreeLibrary(handle) != 0;
}

// static
bool ModuleHandleTraits::IsHandleValid(HMODULE handle)
{
    return handle != nullptr;
}

// static
HMODULE ModuleHandleTraits::NullHandle()
{
    return nullptr;
}

namespace {

    // Implements the UnwindFunctions interface for the corresponding Win32
    // functions.
    class Win32UnwindFunctions : public Win32StackFrameUnwinder::UnwindFunctions {
    public:
        Win32UnwindFunctions();
        ~Win32UnwindFunctions() override;

        PRUNTIME_FUNCTION LookupFunctionEntry(DWORD64 program_counter,
            PDWORD64 image_base) override;

        void VirtualUnwind(DWORD64 image_base,
            DWORD64 program_counter,
            PRUNTIME_FUNCTION runtime_function,
            CONTEXT* context) override;

        ScopedModuleHandle GetModuleForProgramCounter(
            DWORD64 program_counter) override;

    private:
        DISALLOW_COPY_AND_ASSIGN(Win32UnwindFunctions);
    };

    Win32UnwindFunctions::Win32UnwindFunctions() { }
    Win32UnwindFunctions::~Win32UnwindFunctions() { }

    PRUNTIME_FUNCTION Win32UnwindFunctions::LookupFunctionEntry(
        DWORD64 program_counter,
        PDWORD64 image_base)
    {
#ifdef _WIN64
        return RtlLookupFunctionEntry(program_counter, image_base, nullptr);
#else
        NOTREACHED();
        return nullptr;
#endif
    }

    void Win32UnwindFunctions::VirtualUnwind(DWORD64 image_base,
        DWORD64 program_counter,
        PRUNTIME_FUNCTION runtime_function,
        CONTEXT* context)
    {
#ifdef _WIN64
        void* handler_data;
        ULONG64 establisher_frame;
        KNONVOLATILE_CONTEXT_POINTERS nvcontext = {};
        RtlVirtualUnwind(UNW_FLAG_NHANDLER, image_base, program_counter,
            runtime_function, context, &handler_data,
            &establisher_frame, &nvcontext);
#else
        NOTREACHED();
#endif
    }

    ScopedModuleHandle Win32UnwindFunctions::GetModuleForProgramCounter(
        DWORD64 program_counter)
    {
        HMODULE module_handle = nullptr;
        // GetModuleHandleEx() increments the module reference count, which is then
        // managed and ultimately decremented by ScopedModuleHandle.
        if (!::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
                reinterpret_cast<LPCTSTR>(program_counter),
                &module_handle)) {
            const DWORD error = ::GetLastError();
            DCHECK_EQ(ERROR_MOD_NOT_FOUND, static_cast<int>(error));
        }
        return ScopedModuleHandle(module_handle);
    }

} // namespace

// Win32StackFrameUnwinder ----------------------------------------------------

Win32StackFrameUnwinder::UnwindFunctions::~UnwindFunctions() { }
Win32StackFrameUnwinder::UnwindFunctions::UnwindFunctions() { }

Win32StackFrameUnwinder::Win32StackFrameUnwinder()
    : Win32StackFrameUnwinder(WrapUnique(new Win32UnwindFunctions))
{
}

Win32StackFrameUnwinder::~Win32StackFrameUnwinder() { }

bool Win32StackFrameUnwinder::TryUnwind(CONTEXT* context,
    ScopedModuleHandle* module)
{
#ifdef _WIN64
    ScopedModuleHandle frame_module = unwind_functions_->GetModuleForProgramCounter(context->Rip);
    if (!frame_module.IsValid()) {
        // There's no loaded module containing the instruction pointer. This can be
        // due to executing code that is not in a module. In particular,
        // runtime-generated code associated with third-party injected DLLs
        // typically is not in a module. It can also be due to the the module having
        // been unloaded since we recorded the stack.  In the latter case the
        // function unwind information was part of the unloaded module, so it's not
        // possible to unwind further.
        //
        // If a module was found, it's still theoretically possible for the detected
        // module module to be different than the one that was loaded when the stack
        // was copied (i.e. if the module was unloaded and a different module loaded
        // in overlapping memory). This likely would cause a crash, but has not been
        // observed in practice.
        return false;
    }

    ULONG64 image_base;
    // Try to look up unwind metadata for the current function.
    PRUNTIME_FUNCTION runtime_function = unwind_functions_->LookupFunctionEntry(context->Rip, &image_base);

    if (runtime_function) {
        unwind_functions_->VirtualUnwind(image_base, context->Rip, runtime_function,
            context);
        at_top_frame_ = false;
    } else {
        if (at_top_frame_) {
            at_top_frame_ = false;

            // This is a leaf function (i.e. a function that neither calls a function,
            // nor allocates any stack space itself) so the return address is at RSP.
            context->Rip = *reinterpret_cast<DWORD64*>(context->Rsp);
            context->Rsp += 8;
        } else {
            // In theory we shouldn't get here, as it means we've encountered a
            // function without unwind information below the top of the stack, which
            // is forbidden by the Microsoft x64 calling convention.
            //
            // The one known case in Chrome code that executes this path occurs
            // because of BoringSSL unwind information inconsistent with the actual
            // function code. See https://crbug.com/542919.
            //
            // Note that dodgy third-party generated code that otherwise would enter
            // this path should be caught by the module check above, since the code
            // typically is located outside of a module.
            return false;
        }
    }

    module->Set(frame_module.Take());
    return true;
#else
    NOTREACHED();
    return false;
#endif
}

Win32StackFrameUnwinder::Win32StackFrameUnwinder(
    std::unique_ptr<UnwindFunctions> unwind_functions)
    : at_top_frame_(true)
    , unwind_functions_(std::move(unwind_functions))
{
}

} // namespace base
